import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;


public class ConexaoNo extends Thread {
	private ServerSocket socketServidor;
	private Socket socket;
	private BufferedReader streamRecebimento;
	private PrintWriter streamEnvio;
	private ArrayList<InfoArquivos> infoArquivos;
	private boolean infoArquivosOcupado;
	private ArrayList<InfoNos> infoNos;
	private boolean infoNosOcupado;

	/**
	 * 
	 * @param porta: porta a ser criada
	 * @param infoArquivos: lista de arquivos da rede
	 * @param infoNos: lista de nós da rede
	 */
	public ConexaoNo (int porta, ArrayList<InfoArquivos> infoArquivos, ArrayList<InfoNos> infoNos){
		try {

			infoArquivosOcupado = false;
			infoNosOcupado = false;
			socketServidor = new ServerSocket(porta);
			socket = socketServidor.accept();
			streamRecebimento = new BufferedReader(
					new InputStreamReader(
							socket.getInputStream()));
			streamEnvio = new PrintWriter(
					new OutputStreamWriter(
							socket.getOutputStream()));
		} catch (Exception e){
			//system.out.println(e.getMessage());
		}

		this.infoArquivos = infoArquivos;
		this.infoNos = infoNos;
	}
	public void run(){
		try {			
			String stringRecebida;
			String portaGeralNo;

			stringRecebida = streamRecebimento.readLine();

			if (stringRecebida.equals("Ola")) // Se for a primeira vez do no
			{
				// Recebe nome e porta do no
				String nomeNo = streamRecebimento.readLine();
				portaGeralNo = streamRecebimento.readLine();

				adicionarNosNos(nomeNo, Integer.parseInt(portaGeralNo));

				removerArquivosVelhos(nomeNo);

				// Recebe todos os nomes dos arquivos do no
				stringRecebida = streamRecebimento.readLine();
				while (!stringRecebida.equals("Fim"))
				{
					adicionarArquivos(stringRecebida, nomeNo);
					stringRecebida = streamRecebimento.readLine();
				}
			}
			else if(stringRecebida.equals("Update"))
			{
				// Recebe o nome do no que atualizou
				String nomeNo = streamRecebimento.readLine();

				// Recebe o nome arquivo que foi atualizado
				String nomeArquivo = streamRecebimento.readLine();

				// Recebe a operação realizada ("Criado", "Atualizado", "Apagado") 
				String operacao = streamRecebimento.readLine();

				// Se criado ou apagado
				if (operacao.equalsIgnoreCase("Criado") || operacao.equalsIgnoreCase("Apagado")) {
					atualizaListaArquivos(nomeArquivo, nomeNo, operacao);
				}
				else if (operacao.equalsIgnoreCase("Atualizado"))
				{
					// Lista os nós que possuem este arquivo
					ArrayList<InfoNos> listaNos = listaNosComArquivo(nomeArquivo);
					InfoNos noOperacao = getNoPorNome(nomeNo);

					if (listaNos != null){
						for (InfoNos no : listaNos) {

							if (!no.nomeNo.equals(noOperacao.nomeNo)) {
								// Fecha a conexão com o nó atual e abre com o nó escolhido
								fecharConexoes();

								socket = new Socket(no.nomeNo, no.portaGeralNo);

								streamRecebimento = new BufferedReader(
										new InputStreamReader(
												socket.getInputStream()));
								streamEnvio = new PrintWriter(
										new OutputStreamWriter(
												socket.getOutputStream()));

								// Envia o comando
								streamEnvio.println("Comando atualizar");
								streamEnvio.flush();

								// Envia o nome do arquivo a ser criado
								streamEnvio.println(nomeArquivo);
								streamEnvio.flush();

								// Envia o nome do nó que atualizou
								streamEnvio.println(noOperacao.nomeNo);
								streamEnvio.flush();

								// Envia a porta do nó
								streamEnvio.println(noOperacao.portaGeralNo);
								streamEnvio.flush();
							}
						}
					}
				}
			}
			else if (stringRecebida.equals("Lista"))
			{
				ArrayList<String> listaArquivos = listarArquivosNaoRepetir();

				if (listaArquivos != null)
					for(String i : listaArquivos) {
						streamEnvio.println(i);
						streamEnvio.flush();
					}
				streamEnvio.println("Fim");
				streamEnvio.flush();
			}
			else if (stringRecebida.equals("Baixar"))
			{
				String nomeArquivo = streamRecebimento.readLine();

				InfoNos no = procuraArquivo(nomeArquivo);

				// Envia o nome do no
				streamEnvio.println(no.nomeNo);
				streamEnvio.flush();

				// Envia a porta geral do no
				streamEnvio.println(no.portaGeralNo);
				streamEnvio.flush();
			}
			else if (stringRecebida.equals("Criar"))
			{
				// Lê o nome do arquivo
				String nomeArquivo = streamRecebimento.readLine();

				// Escolhe o nó que vai receber o comando para criar arquivo
				InfoNos no = escolheNoParaCriarArquivo();

				if (no != null){
					// Fecha a conexão com o nó atual e abre com o nó escolhido
					fecharConexoes();

					socket = new Socket(no.nomeNo, no.portaGeralNo);

					streamRecebimento = new BufferedReader(
							new InputStreamReader(
									socket.getInputStream()));
					streamEnvio = new PrintWriter(
							new OutputStreamWriter(
									socket.getOutputStream()));

					// Envia o comando
					streamEnvio.println("Comando criar");
					streamEnvio.flush();

					// Envia o nome do arquivo a ser criado
					streamEnvio.println(nomeArquivo);
					streamEnvio.flush();
				}
			}
			else if (stringRecebida.equals("Apagar"))
			{
				// Lê o nome do arquivo
				String nomeArquivo = streamRecebimento.readLine();

				// Lista os nós que possuem o arquivo
				ArrayList<InfoNos> listaNos = listaNosComArquivo(nomeArquivo);

				if (listaNos != null){
					for (InfoNos no : listaNos) {
						// Fecha a conexão com o nó atual e abre com o nó escolhido
						fecharConexoes();

						socket = new Socket(no.nomeNo, no.portaGeralNo);

						streamRecebimento = new BufferedReader(
								new InputStreamReader(
										socket.getInputStream()));
						streamEnvio = new PrintWriter(
								new OutputStreamWriter(
										socket.getOutputStream()));

						// Envia o comando
						streamEnvio.println("Comando apagar");
						streamEnvio.flush();

						// Envia o nome do arquivo a ser criado
						streamEnvio.println(nomeArquivo);
						streamEnvio.flush();
					}
				}
			}
			
			fecharConexoes();

		} catch (Exception e) {
			//system.out.println(/*e.getMessage()*/);
		}
	}

	public synchronized void adicionarArquivos (String nomeArquivo, String nomeNo){
		try {
			while (infoArquivosOcupado)
				Thread.sleep(10);
			infoArquivosOcupado = true;
		} catch (InterruptedException e) {}

		/* Para garantir que não haja arquivos duplicados em um nó, caso sua
		 * conexão caia 
		 */
		if (!infoArquivos.contains(new InfoArquivos(nomeArquivo, nomeNo)))
			infoArquivos.add(new InfoArquivos(nomeArquivo, nomeNo));

		infoArquivosOcupado = false;
	}

	public synchronized void adicionarNosNos(String nomeNo, int portaGeralNo){
		try {
			while (infoNosOcupado)
				Thread.sleep(10);
			infoNosOcupado = true;
		} catch (InterruptedException e) {}

		/* Para garantir que um nó não seja adicionado duas vezes, caso sua
		 * conexão caia 
		 */
		for (InfoNos n : infoNos)
			if (n.nomeNo.equals(nomeNo)) 
				infoNos.remove(n);

		infoNos.add(new InfoNos(nomeNo, portaGeralNo));

		infoNosOcupado = false;
	}

	public synchronized void atualizaListaArquivos(String nomeArquivo, String nomeNo, String operacao){
		try {
			while (infoArquivosOcupado)
				Thread.sleep(10);
			infoArquivosOcupado = true;
		} catch (InterruptedException e) {}

		if (operacao.equalsIgnoreCase("Criado")) {
			infoArquivos.add(new InfoArquivos(nomeArquivo, nomeNo));
		}
		else if (operacao.equalsIgnoreCase("Apagado"))
		{
			if (infoArquivos.size() != 0)
				for (InfoArquivos i : infoArquivos)
					if (i.nomeArquivo.equals(nomeArquivo) && i.nomeNo.equals(nomeNo)) 
						infoArquivos.remove(i);
		}

		infoArquivosOcupado = false;
	}



	public InfoNos procuraArquivo(String nomeArquivo){
		String nomeNo = null;

		try {
			while (infoArquivosOcupado)
				Thread.sleep(10);
			infoArquivosOcupado = true;
		} catch (InterruptedException e) {}

		for (InfoArquivos i : infoArquivos)
			if (i.nomeArquivo.equalsIgnoreCase(nomeArquivo))
			{
				nomeNo = i.nomeNo;
				break;
			}

		infoArquivosOcupado = false;

		try {
			while (infoNosOcupado)
				Thread.sleep(10);
			infoNosOcupado = true;
		} catch (InterruptedException e) {}

		if (nomeNo != null)
		{
			for (InfoNos i : infoNos)
				if (i.nomeNo.equalsIgnoreCase(nomeNo))
				{
					infoNosOcupado = false;
					return i;
				}
		}
		infoNosOcupado = false;
		return null;
	}

	private InfoNos escolheNoParaCriarArquivo(){
		InfoNos no = null;
		try {
			while (infoNosOcupado)
				Thread.sleep(10);
			infoNosOcupado = true;
		} catch (InterruptedException e) {}
		
		try {
			while (infoArquivosOcupado)
				Thread.sleep(10);
			infoArquivosOcupado = true;
		} catch (InterruptedException e) {}

		int[] vetorCount = new int[infoNos.size()];
		for (int i=0; i < infoNos.size(); i++)
			vetorCount[i] = 0;
		
		/*
		 * Faz a contagem de quantos arquivos cada nó tem
		 */
		for (InfoNos n : infoNos) {
			for (InfoArquivos a : infoArquivos)
				if (a.nomeNo.equals(a.nomeNo))
					vetorCount[infoNos.indexOf(n)]++;
		}
		
		/*
		 * Verifica o nó que possui o menor número de arquivos
		 */
		int menorNumero = vetorCount[0];
		int indiceMenor = 0;
		for (int i=1; i < infoNos.size(); i++) {
			if (vetorCount[i] < menorNumero) {
				menorNumero = vetorCount[i];
				indiceMenor = i;
			}
		}
		
		no = infoNos.get(indiceMenor);

		infoArquivosOcupado = false;
		infoNosOcupado = false;
		return no;
	}

	private ArrayList<InfoNos> listaNosComArquivo(String nomeArquivo){
		try {
			while (infoNosOcupado)
				Thread.sleep(10);
			infoNosOcupado = true;
		} catch (InterruptedException e) {}

		try {
			while (infoArquivosOcupado)
				Thread.sleep(10);
			infoArquivosOcupado = true;
		} catch (InterruptedException e) {}

		ArrayList<InfoNos> lista = new ArrayList<InfoNos>();
		ArrayList<String> tempNomeNos = new ArrayList<String>();

		// Pega todos os nós que possuem o arquivo
		for (InfoArquivos i : infoArquivos)			
			if (i.nomeArquivo.equals(nomeArquivo))
				tempNomeNos.add(i.nomeNo);

		// Adiciona as informações destes nós a uma lista
		for (String s : tempNomeNos)
			for (InfoNos i : infoNos)				
				if (i.nomeNo.equals(s))
					lista.add(i);

		infoArquivosOcupado = false;
		infoNosOcupado = false;

		return lista;
	}

	private InfoNos getNoPorNome(String nomeNo){
		try {
			while (infoNosOcupado)
				Thread.sleep(10);
			infoNosOcupado = true;
		} catch (InterruptedException e) {}

		for (InfoNos i : infoNos)
			if (i.nomeNo.equals(nomeNo)) {
				infoNosOcupado = false;
				return i;
			}

		infoNosOcupado = false;
		return null;
	}

	private void removerArquivosVelhos(String nomeNo){
		try {
			while (infoArquivosOcupado)
				Thread.sleep(10);
			infoArquivosOcupado = true;
		} catch (InterruptedException e) {}

		if (infoArquivos.size() != 0)
			for (InfoArquivos n : infoArquivos)
				if (n.nomeNo.equals(nomeNo))
					infoArquivos.remove(n);

		infoArquivosOcupado = false;
	}

	private void fecharConexoes(){
		try {
			socket.close();
			socketServidor.close();
			streamRecebimento.close();
			streamEnvio.close();
		} catch (Exception e){}
	}

	private ArrayList<String> listarArquivosNaoRepetir(){
		ArrayList<String> listaArquivos = new ArrayList<String>();

		try {
			while (infoArquivosOcupado)
				Thread.sleep(10);
			infoArquivosOcupado = true;
		} catch (InterruptedException e) {}

		if (infoArquivos.size() != 0) {
			for (InfoArquivos n : infoArquivos)
				if (!listaArquivos.contains(n.nomeArquivo))
					listaArquivos.add(n.nomeArquivo);
		}
		else
			listaArquivos = null;

		infoArquivosOcupado = true;

		return listaArquivos;
	}
}

